package com.geek.bulk.echo;


import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;

import static com.geek.bulk.util.ClassUtils.isDerived;
import static com.geek.bulk.util.TimeUtils.toTimeUnit;



public interface CustomCircuitBreaker {

    boolean allowRequest();

    void failure(Throwable throwable);

    void markSuccess();

    class Default implements CustomCircuitBreaker {

        final CircuitBreakerProperties circuitBreakerProperties;

        /**
         * 断路器的状态
         */
        private AtomicBoolean circuitOpen = new AtomicBoolean(false);

        /**
         * 断路器最后熔断时的时间点
         */
        private AtomicLong circuitOpenedOrLastTestTime = new AtomicLong();

        /**
         *
         */
        private AtomicLong periodStartTime;

        private LongAdder requestCount = new LongAdder();
        private LongAdder failures = new LongAdder();


        public Default(CircuitBreakerProperties circuitBreakerProperties) {
            this.circuitBreakerProperties = circuitBreakerProperties;
            periodStartTime = new AtomicLong(System.currentTimeMillis());
        }

        @Override
        public boolean allowRequest() {
            return !isOpen() || allowSingleTest();
        }

        @Override
        public void failure(Throwable throwable) {
            requestCount.add(1);
            //断路器没有关闭; 那么就累加错误次数以及错误率
            if (isOnFailure(throwable)) {
                failures.add(1);
            }
        }

        @Override
        public void markSuccess() {
            //断路器开启;关闭断路器;重置所有信息
            if (circuitOpen.get()) {
                circuitOpen.compareAndSet(true, false);
                reset();
            }
            requestCount.add(1);
        }
        private boolean isOnFailure(Throwable throwable) {
            Class<? extends Throwable>[] failOn = circuitBreakerProperties.getFailOn();
            Class<? extends Throwable>[] skipOn = circuitBreakerProperties.getSkipOn();
            return !isDerived(throwable.getClass(), skipOn) && isDerived(throwable.getClass(), failOn);
        }



        private boolean isOpen() {
            //断路器未打开 ;计算上一轮
            if (!circuitOpen.get()) {
                previousCalculateStatus();
            }
            return circuitOpen.get();
        }

        /**
         * 上一轮计算
         */
        private void previousCalculateStatus() {
            TimeUnit timeUnit = toTimeUnit(circuitBreakerProperties.getDelayUnit());
            long duringTime = timeUnit.toMillis(circuitBreakerProperties.getDelay());

            //滑动窗口的结束时间
            long endTime = periodStartTime.get() + duringTime;
            //本轮未结束
            long currentTime = System.currentTimeMillis();

            if (currentTime < endTime) {
                return;
            }
            //配置允许的失败率
            double failureRatio = circuitBreakerProperties.getFailureRatio();
            long failure = failures.sum();

            long sum = requestCount.sum();
            double failureThreshold = (double) failure / sum;
            //失败率达到上线
            if (failureThreshold >= failureRatio) {
                System.out.println("失败率达到上线");
                circuitOpen.set(true);
            }

            //请求容量; 达到容量后会限流
            int requestVolumeThreshold = circuitBreakerProperties.getRequestVolumeThreshold();
            //请求数量达到上限
            if (sum > requestVolumeThreshold) {
                circuitOpen.set(true);
            }
            //上一轮结束
            reset();
        }

        public boolean allowSingleTest() {
            //断路器没有开启;所有请求都允许通过
            if (!circuitOpen.get()) {
                return true;
            }
            long lastTime = circuitOpenedOrLastTestTime.get();

            TimeUnit timeUnit = toTimeUnit(circuitBreakerProperties.getDelayUnit());
            long duringTime = timeUnit.toMillis(circuitBreakerProperties.getDelay());

            long currentTime = System.currentTimeMillis();

            if (lastTime + duringTime < currentTime) {
                circuitOpenedOrLastTestTime.set(currentTime);
                return true;
            }
            return false;
        }

        private void reset() {
            periodStartTime.set(System.currentTimeMillis());
            requestCount.reset();
            failures.reset();
        }

    }

}
